import { YkBabylonScene } from './create3D.ts'
import * as BABYLON from '@babylonjs/core'
import { Scene, Vector3 } from '@babylonjs/core'

/**
 * 添加类似魔兽世界风格的自由移动摄像头
 * @param ykScene - 包含场景和画布等信息的对象
 */
export const addCamera = (ykScene: YkBabylonScene) => {
  const scene: Scene = ykScene.scene
  const canvas: HTMLCanvasElement = ykScene.canvas
  // 角色参数
  const characterHeight = 1.8  // 角色高度（米）
  const characterRadius = 0.3  // 角色半径（米）
  let isFlying = false

  // 创建用于显示的角色胶囊体（可视化角色）
  const displayCapsule = BABYLON.MeshBuilder.CreateCapsule('玩家角色', {
    height: characterHeight,
    radius: characterRadius
  }, scene)
  displayCapsule.isVisible = true // 设置为可见，用于可视化角色

  // 初始化角色位置（在空中某处）
  // const characterPosition = new Vector3(-400, 2, -2)
  const characterPosition = new Vector3(-3, 6, 0)

  // 创建物理角色控制器（用于处理角色的物理行为，如碰撞、重力等）
  const characterController = new BABYLON.PhysicsCharacterController(
    characterPosition,
    { capsuleHeight: characterHeight, capsuleRadius: characterRadius },
    scene
  )

// 将相机位置设置在角色正上方一定高度处
  const cameraHeight = 2 // 相机距离角色的高度
  const cameraPosition = new Vector3(characterPosition.x - 5, characterPosition.y + cameraHeight, characterPosition.z - 4)

  // 创建自由相机（第三人称视角相机）
  const camera = new BABYLON.FreeCamera('视角摄像头', cameraPosition, scene)

  // 设置相机初始位置（在角色后方）
  camera.setTarget(characterPosition) // 相机看向角色

  // 使用物理角色控制器的实现来设置角色控制逻辑
  setupPhysicsCharacterController(camera, characterController, displayCapsule, ykScene, canvas, isFlying)

  // 设置为活动相机（场景中使用的相机）
  scene.activeCamera = camera
}

/**
 * 物理角色控制器设置函数
 * 负责处理角色的移动、跳跃、重力等物理行为
 * @param camera - 相机对象
 * @param characterController - 物理角色控制器
 * @param displayCapsule - 用于显示的角色胶囊体
 * @param scene - Babylon.js 场景对象
 */
function setupPhysicsCharacterController(camera, characterController, displayCapsule, ykScene,
                                         canvas, isFlying) {
  const scene = ykScene.scene
  // 角色状态管理
  let state = 'ON_GROUND' // 角色状态：ON_GROUND(在地面), IN_AIR(在空中), START_JUMP(开始跳跃)
  const inAirSpeed = 18.0     // 空中移动速度
  const onGroundSpeed = 10.0 // 地面移动速度
  let isShiftPressed = false
  const jumpHeight = 1.5     // 跳跃高度
  let wantJump = false       // 是否想要跳跃的标志

  // 输入方向向量（用于存储玩家的移动输入）
  const inputDirection = new Vector3(0, 0, 0)

  // 角色默认朝向（局部空间中的前方向）
  const forwardLocalSpace = new Vector3(0, 0, 1)

  // 角色朝向四元数（用于存储角色的旋转方向），创建一个单位四元数(x=0, y=0, z=0, w=1)， q = w + xi + yj + zk
  let characterOrientation = BABYLON.Quaternion.Identity()

  // 重力向量（模拟地球重力，负值表示向下）
  let characterGravity = new Vector3(0, -9.81, 0)

  // 处理键盘输入事件（监听按键按下和释放）
  scene.onKeyboardObservable.add((kbInfo) => {
    switch (kbInfo.type) {
      // 按键按下事件
      case BABYLON.KeyboardEventTypes.KEYDOWN:
        if (kbInfo.event.key == 'w' || kbInfo.event.key == 'ArrowUp') {
          inputDirection.z = 1  // 向前移动
        } else if (kbInfo.event.key == 's' || kbInfo.event.key == 'ArrowDown') {
          inputDirection.z = -1 // 向后移动
        } else if (kbInfo.event.key == 'a' || kbInfo.event.key == 'ArrowLeft') {
          inputDirection.x = -1 // 向左移动
        } else if (kbInfo.event.key == 'd' || kbInfo.event.key == 'ArrowRight') {
          inputDirection.x = 1  // 向右移动
        } else if (kbInfo.event.key == 'x') {
          if (isFlying) {
            inputDirection.y = -1  // 向下
          }
        } else if (kbInfo.event.key == ' ') {
          if (isFlying) {
            // 如果在飞行，则空间键表示上升高度
            inputDirection.y = 1
          } else {
            wantJump = true       // 请求跳跃
          }
        } else if (kbInfo.event.key.toLowerCase() == 'f8') {
          isFlying = !isFlying
          console.log('切换为' + (isFlying ? '飞行模式' : '重力模式'))

          if (isFlying) {
            characterGravity = new Vector3(0, 0, 0)
          } else {
            characterGravity = new Vector3(0, -9.81, 0)
          }
        } else if (kbInfo.event.key == 'Shift') {
          isShiftPressed = true     // 按下Shift键
        }
        break

      // 按键释放事件
      case BABYLON.KeyboardEventTypes.KEYUP:
        if (kbInfo.event.key == 'w' || kbInfo.event.key == 'ArrowUp') {
          if (inputDirection.z === 1) inputDirection.z = 0  // 停止向前移动
        } else if (kbInfo.event.key == 's' || kbInfo.event.key == 'ArrowDown') {
          if (inputDirection.z === -1) inputDirection.z = 0 // 停止向后移动
        } else if (kbInfo.event.key == 'a' || kbInfo.event.key == 'ArrowLeft') {
          if (inputDirection.x === -1) inputDirection.x = 0 // 停止向左移动
        } else if (kbInfo.event.key == 'd' || kbInfo.event.key == 'ArrowRight') {
          if (inputDirection.x === 1) inputDirection.x = 0  // 停止向右移动
        } else if (kbInfo.event.key == 'x') {
          if (isFlying) {
            inputDirection.y = 0  // 停止向下移动
          }
        } else if (kbInfo.event.key == ' ') {
          if (isFlying) {
            console.log('停止上升高度')
            inputDirection.y = 0 // 停止上升
          } else {
            wantJump = false // 地面模式下确保跳跃请求被清除
          }
        } else if (kbInfo.event.key == 'Shift') {
          isShiftPressed = false    // 释放Shift键
        }
        break
    }
  })


  /**
   * 获取下一个角色状态
   * 根据当前状态和物理支持信息确定角色的下一个状态
   * @param supportInfo - 物理支持信息（是否接触地面等）
   * @returns 下一个状态字符串
   */
  const getNextState = function(supportInfo: {
    supportedState: BABYLON.CharacterSupportedState;
    averageSurfaceNormal?: Vector3;
    averageSurfaceVelocity?: Vector3;
  }): string {
    // 如果在飞行模式下，始终保持在空中状态
    if (isFlying) {
      return 'IN_AIR'
    }
    if (state == 'IN_AIR') {
      // 如果当前在空中，检查是否接触地面
      if (supportInfo.supportedState == BABYLON.CharacterSupportedState.SUPPORTED) {
        return 'ON_GROUND'  // 接触到地面，变为在地面状态
      }
      return 'IN_AIR'       // 仍在空中
    } else if (state == 'ON_GROUND') {
      // 如果当前在地面，检查是否失去支撑
      if (supportInfo.supportedState != BABYLON.CharacterSupportedState.SUPPORTED) {
        return 'IN_AIR'     // 失去支撑，变为在空中状态
      }

      // 检查是否请求跳跃
      if (wantJump) {
        return 'START_JUMP' // 请求跳跃，变为开始跳跃状态
      }
      return 'ON_GROUND'    // 继续在地面
    } else if (state == 'START_JUMP') {
      return 'IN_AIR'       // 跳跃开始后变为在空中状态
    }

    // 添加默认返回值，防止函数在某些情况下返回 undefined
    return state
  }

  /**
   * 计算期望速度
   * 根据角色状态和输入计算角色应该具有的速度
   * @param deltaTime - 帧时间间隔
   * @param supportInfo - 物理支持信息
   * @param characterOrientation - 角色朝向
   * @param currentVelocity - 当前速度
   * @returns 计算出的期望速度向量
   */
  const getDesiredVelocity = function(deltaTime, supportInfo, characterOrientation, currentVelocity) {
    // 获取并更新下一个状态
    let nextState = getNextState(supportInfo)
    if (nextState != state) {
      state = nextState
    }

    // 计算世界坐标系中的向上方向（重力的反方向）
    let upWorld = characterGravity.normalizeToNew()
    upWorld.scaleInPlace(-1.0)

    // 计算世界坐标系中的前方向（根据角色朝向旋转局部前方向）
    let forwardWorld = forwardLocalSpace.applyRotationQuaternion(characterOrientation)

    if (isFlying) {
      // 飞行模式处理
      let desiredVelocity = inputDirection.scale(inAirSpeed).applyRotationQuaternion(characterOrientation)
      if (isShiftPressed) {
        desiredVelocity.scaleInPlace(5.0)
      }
      // 在飞行模式下，直接使用期望速度并添加到当前速度上，实现平滑过渡效果
      let outputVelocity = currentVelocity.clone()
      // 在飞行模式下，使用物理控制器计算移动以实现平滑减速效果
      // 计算速度变化量
      let velocityChange = desiredVelocity.subtract(currentVelocity)
      // 应用阻尼系数，使速度变化更加平滑
      const dampingFactor = 5.0 // 阻尼系数，可以调整这个值来改变平滑度
      velocityChange.scaleInPlace(dampingFactor * deltaTime)
      // 应用速度变化
      outputVelocity.addInPlace(velocityChange)

      // 不应用重力
      return outputVelocity
    } else if (state == 'IN_AIR') {
      // 空中状态处理
      // 根据输入方向和角色朝向计算期望速度
      let desiredVelocity = inputDirection.scale(inAirSpeed).applyRotationQuaternion(characterOrientation)

      // 使用物理控制器计算移动
      let outputVelocity = characterController.calculateMovement(
        deltaTime, // 时间增量（帧时间）
        forwardWorld, // 世界坐标系中的前方向
        upWorld, // 表面法线向量
        currentVelocity, // 当前速度
        BABYLON.Vector3.ZeroReadOnly, // 表面速度（如移动平台）
        desiredVelocity, // 期望速度（用户输入决定）
        upWorld // 世界坐标系中的"向上"方向
      )

      outputVelocity.addInPlace(upWorld.scale(-outputVelocity.dot(upWorld)))
      outputVelocity.addInPlace(upWorld.scale(currentVelocity.dot(upWorld)))

      // 添加重力影响
      outputVelocity.addInPlace(characterGravity.scale(deltaTime))
      return outputVelocity

    } else if (state == 'ON_GROUND') {
      // 地面状态处理
      // 相对于站立表面移动角色
      let desiredVelocity = inputDirection.scale(onGroundSpeed).applyRotationQuaternion(characterOrientation)
      if (isShiftPressed) {
        desiredVelocity.scaleInPlace(5.0)
      }
      // 使用物理控制器计算地面移动
      let outputVelocity = characterController.calculateMovement(
        deltaTime, // 时间增量（帧时间）
        forwardWorld, // 世界坐标系中的前方向
        supportInfo.averageSurfaceNormal, // 表面法线向量
        currentVelocity, // 当前速度
        supportInfo.averageSurfaceVelocity,// 表面速度（如移动平台）
        desiredVelocity,// 期望速度（用户输入决定）
        upWorld // 世界坐标系中的"向上"方向
      )

      // 水平投影处理：AI说水平投影处理在以下情况才会有显著效果：
      // 1. 陡峭斜面
      // 当斜面角度接近垂直时（比如70-80度），没有水平投影处理会导致：
      // 角色在陡坡上移动困难或无法移动
      // 移动速度在垂直方向上分量过大，导致角色"漂浮"感
      // 2. 复杂表面（非平面）
      // 在以下表面情况下：
      // 弯曲的管道内壁
      // 复杂几何形状表面
      // 动态变化的表面角度
      // 3. 高速移动
      // 当角色以较高速度移动时：
      // 物理计算的误差会被放大
      // 角色可能会意外飞离表面
      // 4. 移动平台
      // 当角色站在移动平台上时：
      // 平台本身有复杂运动（旋转、加速等）
      // 需要精确计算相对速度
      // 我测试这段代码注释掉问题不大
      // outputVelocity.subtractInPlace(supportInfo.averageSurfaceVelocity) // 从角色速度中减去表面本身的速度（比如站在移动的平台上）
      // let inv1k = 1e-3
      //
      // // 检查角色的速度是否有一个向上的分量（也就是说角色似乎要飞离地面），
      // // 这通常不应该发生，因为角色是在地面上的。如果发生这种情况，说明需要进行修正。
      // // 如果速度在向上方向上的分量大于阈值
      // if (outputVelocity.dot(upWorld) > inv1k) {
      //     let velLen = outputVelocity.length() // 向量的长度，也就是模
      //
      //     // 获取水平方向上的期望长度
      //     let horizLen = velLen / supportInfo.averageSurfaceNormal.dot(upWorld)
      //
      //     // 将速度重新投影到水平面上
      //     let c = supportInfo.averageSurfaceNormal.cross(outputVelocity)
      //     outputVelocity = c.cross(upWorld)
      //     outputVelocity.scaleInPlace(horizLen)
      // }
      //
      // // 添加表面速度影响
      // outputVelocity.addInPlace(supportInfo.averageSurfaceVelocity)
      // console.log(outputVelocity)
      return outputVelocity

    } else if (state == 'START_JUMP') {
      // 跳跃状态处理
      // 计算跳跃初速度（基于所需跳跃高度）
      let u = Math.sqrt(2 * characterGravity.length() * jumpHeight)
      let curRelVel = currentVelocity.dot(upWorld)

      // 返回跳跃后的速度
      return currentVelocity.add(upWorld.scale(u - curRelVel))
    }

    // 默认返回零速度
    return new Vector3(0, 0, 0)
  }

  // 相机旋转控制
  let isMouseDown = false

  // 使用Babylon.js的指针事件系统监听鼠标事件
  scene.onPointerObservable.add((pointerInfo) => {
    switch (pointerInfo.type) {
      case BABYLON.PointerEventTypes.POINTERDOWN:
        isMouseDown = true
        break

      case BABYLON.PointerEventTypes.POINTERUP:
        isMouseDown = false
        break

      case BABYLON.PointerEventTypes.POINTERMOVE:
        if (isMouseDown) {
          // 保存当前目标点
          let tgt = camera.getTarget().clone()
          // 根据鼠标移动调整相机位置
          const yawDelta = pointerInfo.event.movementX * 0.002
          const pitchDelta = pointerInfo.event.movementY * 0.002

          // 获取相机（角色）当前朝向
          const cameraForward = camera.getForwardRay().direction.clone()
          // 只考虑水平方向，忽略Y轴
          cameraForward.y = 0
          cameraForward.normalize()

          // 计算角色朝向的角度
          const charYaw = Math.atan2(cameraForward.x, cameraForward.z)

          // 创建基于角色朝向的旋转矩阵
          const charRotationY = BABYLON.Matrix.RotationY(charYaw) // 将世界坐标系旋转到角色坐标系的矩阵
          const inverseCharRotationY = BABYLON.Matrix.RotationY(-charYaw) // 将角色坐标系旋转回世界坐标系的矩阵
          // console.log(`charRotationY = ${charRotationY}`)
          // console.log(`inverseCharRotationY = ${inverseCharRotationY}`)

          // 创建相机旋转矩阵
          const camRotation = BABYLON.Matrix.RotationYawPitchRoll(yawDelta, pitchDelta, 0)

          // 在角色本地坐标系中应用旋转
          const finalRotation = inverseCharRotationY.multiply(camRotation).multiply(charRotationY)

          // 应用旋转到相机位置
          camera.position = Vector3.TransformCoordinates(camera.position.subtract(tgt), finalRotation).add(tgt)
          // 保持目标点不变
          camera.setTarget(tgt)
        }
        break
    }
  })

  // 相机跟随逻辑
  /**
   * scene.onBeforeRenderObservable 在每一帧渲染之前执行，它属于主渲染循环的一部分。这个回调函数会在场景渲染之前被调用，通常用于：
   * 更新相机位置和目标
   * 同步显示对象的位置（如你代码中的胶囊体）
   * 处理输入控制逻辑
   * 执行任何需要在渲染前完成的计算
   * 这个回调在每次渲染循环中都会执行一次，频率通常与屏幕刷新率一致（如60Hz）。
   */
  scene.onBeforeRenderObservable.add(() => {
    const frameId = scene.getFrameId()
    // 根据相机旋转更新角色朝向
    BABYLON.Quaternion.FromEulerAnglesToRef(0, camera.rotation.y, 0, characterOrientation)

    // 同步显示胶囊体与物理控制器的位置
    const charPosition = characterController.getPosition()
    displayCapsule.position.copyFrom(charPosition)

    // 相机跟随逻辑
    let cameraDirection = camera.getDirection(new BABYLON.Vector3(0, 0, 1)) // 获取相机朝向，也就是当前的角色朝向
    // cameraDirection.y = 0 // 不能设置y=0，否则从高空落下时，镜头乱转
    cameraDirection.normalize()

    // 确保相机始终在角色后方固定距离处，而不是通过setTarget改变朝向
    // 计算理想相机位置（在角色后方）8个单位距离
    camera.position = charPosition.subtract(cameraDirection.scale(8))

    // ------------------------------- 根据距离禁用或启用鼠标拾取功能 -------------------------------
    if (!camera || !ykScene.customData.pickableMeshes) return

    // 每60帧检查一次，减少性能开销
    if (frameId % 60 !== 0) return

    for (const cart of ykScene.customData.pickableMeshes) {
      const distance = BABYLON.Vector3.Distance(camera.position, cart.position)
      // 根据距离动态启用/禁用拾取功能
      cart.isPickable = distance <= 30
      cart.getPhysicsBody()
    }
    // ------------------------------- 根据距离禁用或启用鼠标拾取功能 -------------------------------
  })

  // 物理更新后，计算并设置新速度，更新角色控制器状态
  /**
   * scene.onAfterPhysicsObservable 在物理引擎更新之后执行。在 Babylon.js 中，典型的帧更新顺序如下：
   * 处理输入事件
   * 执行物理模拟更新（如果启用了物理引擎）
   * 执行 onAfterPhysicsObservable 回调
   * 执行 onBeforeRenderObservable 回调
   * 渲染场景
   * onAfterPhysicsObservable 主要用于：
   * 根据物理计算结果更新对象状态
   * 处理与物理相关的逻辑（如碰撞检测后的响应）
   * 更新角色控制器等物理相关组件
   */
  scene.onAfterPhysicsObservable.add((_) => {
    // 检查帧时间是否有效
    if (scene.deltaTime == undefined) return
    let dt = scene.deltaTime / 1000.0  // 转换为秒
    if (dt == 0) return

    // 检查角色是否受到支撑（检测是否接触地面）
    const down = new Vector3(0, -1, 0)  // 向下方向
    const support = characterController.checkSupport(dt, down)

    // 根据相机的Y轴旋转更新角色朝向
    // 这样角色移动方向会与相机朝向一致
    // 这行代码的作用是根据欧拉角创建一个四元数，并将其存储在characterOrientation变量中。
    BABYLON.Quaternion.FromEulerAnglesToRef(0, camera.rotation.y, 0, characterOrientation)

    // 计算期望的线性速度
    const desiredLinearVelocity = getDesiredVelocity(
      dt,
      support,
      characterOrientation,
      characterController.getVelocity()
    )

    // 设置角色控制器的速度
    characterController.setVelocity(desiredLinearVelocity)

    // 集成物理更新（应用速度和重力等）
    characterController.integrate(dt, support, isFlying ? new Vector3(0, 0, 0) : characterGravity)

    // 重置跳跃请求（跳跃只执行一次）
    if (wantJump) {
      wantJump = false
    }
  })
}
